/******************************************************************************

    Copyright (C) 2006 Alessio Stalla (alessiostalla@gmail.com)

    This file is part of PrettyProlog.

    PrettyProlog is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    PrettyProlog is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PrettyProlog; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    See the file COPYING for further details.

 *****************************************************************************/

package prettyprolog.gui;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import prettyprolog.datatypes.Callable;
import prettyprolog.datatypes.Constraint;
import prettyprolog.datatypes.Real;
import prettyprolog.datatypes.Substitution;
import prettyprolog.datatypes.Term;
import prettyprolog.datatypes.Variable;
import prettyprolog.engine.Domain;
import prettyprolog.engine.Engine;
import prettyprolog.engine.Goal;
import prettyprolog.engine.NotAClauseException;
import prettyprolog.engine.listeners.EngineAdapter;
import prettyprolog.engine.listeners.EngineEvent;
import prettyprolog.engine.listeners.StackEvent;
import prettyprolog.engine.listeners.StackListener;
import prettyprolog.parser.EndOfStreamException;
import prettyprolog.parser.ParseException;

/**
 * The main GUI class, it can be used as a stand-alone applet or added to
 * another component such as a JFrame.<br />
 * It contains a JSplitPane that splits it into two sections, left and right,
 * that contain two JTabbedPanes.
 * PrettyPrologPanels can be added to each section's tabbed pane, and can also
 * customize the menu bar associated to this applet.
 */
public class PrettyPrologApplet extends JApplet {

	private static final long serialVersionUID = 1L;

	/** The Engine associated with this applet. */
    private Engine engine = new Engine();
    
    private boolean is_applet = true;
    private PrettyPrologApplet self = this;
    private Goal goal;
    private boolean solved = false;
    private boolean step_by_step = false;
    private static String newline = System.getProperty("line.separator", "\n");

    private JMenuBar menubar = new JMenuBar();
    public static final int FILE_MENU    = 0;
    public static final int OPTIONS_MENU = 1;
    
    private JTextField input = new JTextField(25);
    private JTextArea output = new JTextArea();
    private JTextArea output1 = new JTextArea();				
    private JTextArea output2 = new JTextArea();
    private boolean show_hidden_variables = false;
    
    //Control buttons
    private JButton solve_b = new JButton("Solve");
    private JButton continue_b = new JButton("Continue");
    private JButton stop_b = new JButton("Stop");
    private JButton clear_b = new JButton("Clear");

    //Panels
    /** Identifies the possible positions of PrettyPrologPanels. */
    public enum PanelPosition { LEFT, RIGHT };
    
    private JTabbedPane left_panels = new JTabbedPane();
    private JTabbedPane right_panels = new JTabbedPane();
    private java.util.List<PrettyPrologPanel> left_panels_list = new ArrayList<PrettyPrologPanel>();
    private java.util.List<PrettyPrologPanel> right_panels_list = new ArrayList<PrettyPrologPanel>();
    
    private PrettyPrologPanel main_p = new PrettyPrologPanel("Main");
    
    private MultipleButtonToggler show_hidden_toggler =
        new MultipleButtonToggler(show_hidden_variables) {
            
            protected void handleAction(ActionEvent e) {
                for(PrettyPrologPanel p : left_panels_list) {
                    p.showHiddenVariables(getState());
                }
                for(PrettyPrologPanel p : right_panels_list) {
                    p.showHiddenVariables(getState());
                }
            }
        
        };
        
        private MultipleButtonToggler step_toggler =
            new MultipleButtonToggler(step_by_step) {

                protected void handleAction(ActionEvent e) {
                    step_by_step = getState();
                    if(step_by_step)
                        continue_b.setToolTipText("Proceed to next step");
                    else continue_b.setToolTipText("Search for alternate solutions");
                }

            };
    
    /** Constructs a new PrettyPrologApplet. */
    public PrettyPrologApplet() {
        solve_b.setToolTipText("Start solving the goal");
        continue_b.setToolTipText("Search for alternate solutions");
        stop_b.setToolTipText("Abort solving");
        clear_b.setToolTipText("Clear the current goal");
	continue_b.setEnabled(false);
	stop_b.setEnabled(false);
	clear_b.setEnabled(true);
	setupListeners();
	setupMenuBar();
    }
    
    private PrettyPrologPanel constr = new PrettyPrologPanel("Constraint Viewer - Arc Cons");
    
    private PrettyPrologPanel constr2 = new PrettyPrologPanel("Constraint Viever - Bound Cons");
    
   
    /**
     * Adds the default PrettyPrologPanels, i.e.
     * <ul>
     *   <li>output</li>
     *   <li>stack viewer</li>
     *   <li>SLD viewer</li>
     *   <li>theory viewer</li>
     * </ul>
     */
    protected void addDefaultPanels() {
        addPanel(main_p, PanelPosition.LEFT, false);
 //       addPanel(new StackViewer(), PanelPosition.LEFT, true);
        addPanel(constr, PanelPosition.LEFT, false);
        addPanel(constr2, PanelPosition.LEFT, false);
        addPanel(new SLDViewer(), PanelPosition.LEFT, true);
        addPanel(new TheoryViewer(engine.getParser()), PanelPosition.RIGHT, true);
    }
    
    /**
     * Returns all the panels that have been added to this applet,
     * including default ones.
     */
    public java.util.List<PrettyPrologPanel> getPanels() {
        java.util.List<PrettyPrologPanel> list = new ArrayList<PrettyPrologPanel>(left_panels_list);
        list.addAll(right_panels_list);
        return list;
    }
    
    /** @return the Engine associated to this applet. */
    public Engine getEngine() {
        return engine;
    }
    
    /**
     * Sets whether you're running PrettyProlog in applet mode (some options
     * are disabled, this is the default) or in full app mode.<br />
     * <b>WARNING</b> turning off applet mode while effectively running
     * PrettyProlog as an applet might raise security exceptions.
     * @param b true to set Applet Mode on, false to set it off.
     */
    public void isApplet(boolean b) {
	is_applet = b;
    }

    /**
     * @return true if PrettyProlog has been specified to run as an applet,
     * false otherwise.
     */
    public boolean isApplet() {
	return is_applet;
    }

    /** Same effect as <code>addPanel(pnl, PanelPosition.LEFT, true)</code>. */
    public void addPanel(PrettyPrologPanel pnl) {
        addPanel(pnl, PanelPosition.LEFT, true);
    }
    
    /**
     * Adds a PrettyPrologPanel to the specified side (left or right)
     * and notifies it that it now belongs to this PrettyPrologApplet,
     * calling its setOwner() method.
     * @param pnl the panel to add.
     * @param pos the position (LEFT or RIGHT) where to add the panel.
     * @param add_scroll_pane set this to true if you want a JScrollPane
     *        to be added automatically around your panel.
     */
    public void addPanel(PrettyPrologPanel pnl, PanelPosition pos,
                         boolean add_scroll_pane) {
        switch(pos) {
            default:
            case LEFT:
                if(add_scroll_pane)
                    left_panels.add(new JScrollPane(pnl), pnl.getName());
                else left_panels.add(pnl, pnl.getName());
                left_panels_list.add(pnl);
                break;
            case RIGHT:
                if(add_scroll_pane)
                    right_panels.add(new JScrollPane(pnl), pnl.getName());
                else right_panels.add(pnl, pnl.getName());
                right_panels_list.add(pnl);
                break;
        }
        pnl.setOwner(this);
    }
    
    /**
     * Removes a panel from the applet, if it was previously added.
     * Else, it does nothing.
     */
    public void removePanel(PrettyPrologPanel pnl) {
        int index = left_panels_list.indexOf(pnl);
        if(index != -1) {
            left_panels_list.remove(pnl);
            left_panels.removeTabAt(index);
            pnl.removeFromOwner();
        } else {
            index = right_panels_list.indexOf(pnl);
            if(index != -1) {
                right_panels_list.remove(pnl);
                right_panels.removeTabAt(index);
                pnl.removeFromOwner();
            }
        }
    }
    
    /**
     * Performs applet initialization, i.e. places GUI components on the
     * screen. This method MUST be called EXACTLY once - if you run
     * PrettyProlog as an applet the Java plugin or applet viewer will
     * call this method for you, otherwise you should call it manually.
     */
    public void init() {
	JPanel tmp_panel;
        main_p.setLayout(new BorderLayout());
        constr.setLayout(new BorderLayout());
        constr2.setLayout(new BorderLayout());
        
	JPanel global_p = new JPanel(new BorderLayout());
        JSplitPane split_pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        split_pane.setResizeWeight(1);
        output.setEditable(false);
        main_p.add(new JScrollPane(output));
        constr.add(new JScrollPane(output1));
        constr2.add(new JScrollPane(output2));

        split_pane.setRightComponent(right_panels);
        split_pane.setLeftComponent(left_panels);
        
	global_p.add(split_pane);

	JPanel controls_p = new JPanel();
	controls_p = new JPanel(new BorderLayout());
	controls_p.add(new JLabel("?-"), BorderLayout.WEST);
	controls_p.add(input);
	controls_p.add(solve_b, BorderLayout.EAST);
	tmp_panel = new JPanel(new FlowLayout());
        JCheckBox step_by_step_box = new JCheckBox("Step by step");
        step_toggler.addButton(step_by_step_box);
	tmp_panel.add(step_by_step_box);
        tmp_panel.add(continue_b);
	tmp_panel.add(stop_b);
	tmp_panel.add(clear_b);
        JCheckBox show_hidden_vars_b = new JCheckBox("Show hidden variables");
        show_hidden_toggler.addButton(show_hidden_vars_b);
        for(PrettyPrologPanel p : left_panels_list) {
            p.showHiddenVariables(show_hidden_variables);
        }
        for(PrettyPrologPanel p : right_panels_list) {
            p.showHiddenVariables(show_hidden_variables);
        }
        tmp_panel.add(show_hidden_vars_b);
	controls_p.add(tmp_panel, BorderLayout.SOUTH);
	global_p.add(controls_p, BorderLayout.SOUTH);

	setContentPane(global_p);
        addDefaultPanels();
    }

    /**
     * Tries to load a Theory from a file on disk. This operation fails
     * silently (without throwing exceptions) if PrettyProlog is running in
     * applet mode. If loading succeeds, the engine's Theory is set to be
     * the loaded Theory.
     * @param f the File from which to load the theory.
     * @return true if the operation succeeded, false otherwise.
     */
    public boolean loadTheory(File f)
	throws IOException, ParseException, NotAClauseException {
	if(is_applet)
	    return false;
        engine.resetTheory();
	engine.loadTheory(new FileReader(f));
	return true;
    }

    //Shows solving result.
    private void showSolved(boolean solved) {
    	if(solved) {
    		 output.append(" yes" + newline + newline);
    	} else {
    		output.append(" no" + newline + newline);
    	}
    }

    private void setupListeners() {
	//Step-by-step execution
	engine.getStack().addStackListener(new StackListener() {
		
		public void framePopped(StackEvent e) {
		    if(step_by_step)
			engine.pause();
		}

		public void frameCut(StackEvent e) {
		    if(step_by_step)
			engine.pause();
		}

		public void framePushed(StackEvent e) {
		    if(step_by_step)
			engine.pause();
		}

		public void stackCleared(StackEvent e) {}
                
	    });

	engine.setIOManager(new Engine.IOManager() {

		public String input() {
		    String s = null;
		    while(s == null)
			s = JOptionPane.showInputDialog(self, "Input", "");
		    return s;
		}

		public void output(String msg) {
		    output.append(msg);
		}
		
        public void output1(String msg) {
            output1.append(msg);
        }

        public void output2(String msg) {
            output2.append(msg);
        }

		public void error(String msg) {
		    output.append("error> " + msg + newline);
		}
	    });

	/*
	 * When a solution is found:
	 *   if the goal is ground, print "yes" and stop.
	 *   else print the current Substitution followed by "?",
	 *        and wait for user action (either "continue" or "stop").
	 * Also the flag "solved" is set to true so that when the user
	 * clicks on the Continue button we know if a solution was found
	 * (solved == true) or not (solved == false).
	 */
	engine.addEngineListener(new EngineAdapter() {
 /*       
		public void solutionFound(EngineEvent e) {
			output.append("SolutionFound-1"+"\n");
		    engine.pause();
		    solved = true;
		    if(goal.isGround()) {
		    	output.append("SolutionFound-2"+"\n");
		    	showSolved(true);
		    	solve_b.setEnabled(true);
		    	clear_b.setEnabled(true);
		    }
		    else {
		    	output.append("SolutionFound-3"+"\n");
		    	String str = e.getSubstitution().toString(false);
		    	output.append(str + " ?");
		    	solve_b.setEnabled(false);
		    	continue_b.setEnabled(true);
		    	stop_b.setEnabled(true);
		    }
		}
	    });
*/           
		public void solutionFound(EngineEvent e) {
		    engine.pause();
		    solved = true;
		    if(goal.isGround()) {
		    	showSolved(true);
		    	solve_b.setEnabled(true);
		    	clear_b.setEnabled(true);
		    } else {
		    	
		    	String str = e.getSubstitution().toString(false);
		    	String cons = e.getConstraint().toString();
		    	List<Term> args = e.getConstraint().getCons();
		    	if(engine.getDomini().size() != 0) {
			    	Vector vect = new Vector();
			    	for(Term t : args) {
			    		if (t instanceof Callable) {
			    			engine.VariableCount((Callable) t, vect);
			    		}
			    	}
			    	Substitution su = e.getSubstitution();
			    	Constraint c = e.getConstraint();
			        List<Term> ls = c.getCons();
			        Vector v5 = new Vector();
			        for (Term t : ls) {                                                     // metto in v tutte le var del constraint
			            if (t instanceof Callable) {
			                engine.VariableCount ((Callable) t, v5);
			            }
			        }

			        Vector bdom = engine.getStack().get(0).getBoundDomain();
			        Vector adom = engine.getStack().get(0).getArcDomain();
			        
			        Vector v = engine.removeDupl(v5);
			        Vector v1 = su.getVarList();
			        Vector v2 = su.getVariabili();

			        for (int i=0; i < v2.size(); i++) {
			        	if(v2.get(i) instanceof Variable) {
			        		Variable q = ((Variable) v2.get(i));
			        		Vector domi = new Vector();
			        		if(su.getSubst(q) instanceof Variable) {
			        			if(bdom.contains(su.getSubst(q))) {
			        				Variable q1 = ((Variable) su.getSubst(q));
			        				int index = bdom.indexOf(q1);
			        				domi = ((Variable) bdom.get(index)).dom;
			        			} else if (bdom.contains(q)) {
			        				int index = bdom.indexOf(q);
			        				domi = ((Variable) bdom.get(index)).dom;
			        			} else {
			        				domi = ((Variable) su.getSubst(q)).dom;
			        			}
			        		} else if (su.getSubst(q) instanceof Real) {
			        			int a = (int) ((Real) su.getSubst(q)).getValue();
			        			if(bdom.contains(q)) {
				        			int index = bdom.indexOf(q);
				        			Vector temp = new Vector();
				        			Vector domtemp = ((Variable) bdom.get(index)).dom;
				        			if(domtemp.contains(a)) {
				        				temp.add(a);
				        				domi = temp;
				        			} else {
				        				domi = temp;
				        			}
			        			} else {
			        				domi = q.dom;
			        			}
//			        			domi = q.dom;
			        		}
//			        		if(!domi.isEmpty()) {
			        			output.append("  "+q.toString()+" in "+engine.ToString(domi)+"\n");
//			        		}
			        	}
			        	
			        }
		    	}

	        	
	        	if(str.equals("true")) {
	        		showSolved(true);
	        		solve_b.setEnabled(true);
			    	continue_b.setEnabled(false);
			    	stop_b.setEnabled(false);
	        	} else {
//	        		output.append(str +" ?"+"\n");
	        		if(engine.domainEmpty() || engine.getDomini().size()==0) {
	        			output.append(str +" ?"+"\n");
	        		} else {
	        			output.append(" ?"+"\n");
	        		}
	        		solve_b.setEnabled(false);
			    	continue_b.setEnabled(true);
			    	stop_b.setEnabled(true);
	        	}
		    }
		}
	    });

	solve_b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                solve();
            }
        });
        
	/*
	 * The user can "continue" if:
	 *   a) a solution was found, or
	 *   b) we're in step-by-step mode and a step has completed.
	 * This isn't a trivial task because the user can pass in any moment
	 * from "normal" mode to step-by-step mode, so particular care must be
	 * put in maintaing the state consistent.
	 */
	continue_b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                continueSolving();
            }
        });
        
        input.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if(continue_b.isEnabled())
                    continueSolving();
                else if(solve_b.isEnabled())
                    solve();
            }
        });
             
	stop_b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                engine.pause();
                continue_b.setEnabled(false);
                stop_b.setEnabled(false);
                output.append(newline);
                showSolved(solved);
                solved = false;
                solve_b.setEnabled(true);
                clear_b.setEnabled(true);
            }
        });
            
	clear_b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                engine.abort();
                continue_b.setEnabled(false);
                stop_b.setEnabled(false);
                input.setText("");
                solve_b.setEnabled(true);
            }
        });
    }
    
    private void solve() {
        if(input.getText().equals("")) {
            return;
        }
        try {
        	goal = (Goal) engine.getParser().parse(engine.getParser().Goal(), input.getText());
        } catch(EndOfStreamException _e) {
            _e.printStackTrace();
            JOptionPane.showMessageDialog(self, "Parse Error " + _e, "Error", JOptionPane.ERROR_MESSAGE);
            solve_b.setEnabled(true);
            return;
        } catch(ParseException _e) {
            _e.printStackTrace();
            JOptionPane.showMessageDialog(self, "Parse Error " + _e, "Error", JOptionPane.ERROR_MESSAGE);
            solve_b.setEnabled(true);
            return;
        } catch(IllegalArgumentException _e) {
            _e.printStackTrace();
            JOptionPane.showMessageDialog(self, "A goal must be a list of Callables", "Error", JOptionPane.ERROR_MESSAGE);
            solve_b.setEnabled(true);
            return;
        }
        solve_b.setEnabled(false);
        clear_b.setEnabled(false);
        solved = false;
        engine.abort();
        output.append("?- " + goal + newline);
        try {
            engine.solve(goal);
            if(!solved && !step_by_step) {
                showSolved(false);
                solve_b.setEnabled(true);
                clear_b.setEnabled(true);
            }
        } catch(OutOfMemoryError _e) {
            JOptionPane.showMessageDialog(self, "Out of memory error - probably caused by infinite recursion", "Error", JOptionPane.ERROR_MESSAGE);
                solve_b.setEnabled(true);
                clear_b.setEnabled(true);
                return;
        } catch(StackOverflowError _e) {
            JOptionPane.showMessageDialog(self, "Stack overflow error - probably caused by infinite recursion", "Error", JOptionPane.ERROR_MESSAGE);
                solve_b.setEnabled(true);
                clear_b.setEnabled(true);
                return;
        }
        if(step_by_step) {
            //Allow the user to continue/stop after a single step
            stop_b.setEnabled(true);
            continue_b.setEnabled(true);
        }
    }
    
    private void continueSolving() {
        if(!continue_b.isEnabled())
            return;
        if(!step_by_step) {
            if(solved) //case a)
                output.append(" ;" + newline);
            solved = false;
            continue_b.setEnabled(false);
            stop_b.setEnabled(false);
            engine.continueSolving();
            if(!solved) { //No more solutions were found
                showSolved(false);
                solve_b.setEnabled(true);
                clear_b.setEnabled(true);
            }
        } else { //case b)
            solved = false;
            engine.continueSolving();
            if(solved) {
                if(goal.isGround()) {
                    continue_b.setEnabled(false);
                    stop_b.setEnabled(false);
                    solve_b.setEnabled(true);
                    clear_b.setEnabled(true);
                } else {
                    output.append(" ;" + newline);
                    if(engine.getStack().isEmpty()) {
                        continue_b.setEnabled(false);
                        stop_b.setEnabled(false);
                        showSolved(false);
                        solve_b.setEnabled(true);
                        clear_b.setEnabled(true);
                    }
                }
                return;
            }
            if(engine.getStack().isEmpty()) {
                continue_b.setEnabled(false);
                stop_b.setEnabled(false);
                showSolved(solved);
                solve_b.setEnabled(true);
                clear_b.setEnabled(true);
            }
        }
    }
    
    private void setupMenuBar() {
        JMenu file_menu = new JMenu("File");
        menubar.add(file_menu);
        JMenu options_menu = new JMenu("Options");
        final JCheckBoxMenuItem step_by_step_item =
            new JCheckBoxMenuItem("Step by step");
        step_by_step_item.addChangeListener(new ChangeListener() {
        
            public void stateChanged(ChangeEvent e) {
                if(step_by_step_item.isArmed())
                    showStatus("If enabled, stop solving at each push or pop on the stack.");
                else showStatus("");
            }
            
        });
        step_toggler.addButton(step_by_step_item);
        options_menu.add(step_by_step_item);
        final JCheckBoxMenuItem show_hidden_item =
            new JCheckBoxMenuItem("Show Hidden Variables");
        show_hidden_item.addChangeListener(new ChangeListener() {
        
            public void stateChanged(ChangeEvent e) {
                if(show_hidden_item.isArmed())
                    showStatus("If enabled, show variables automatically generated by the Engine.");
                else showStatus("");
            }
            
        });
        show_hidden_toggler.addButton(show_hidden_item);       
        options_menu.add(show_hidden_item);
        menubar.add(options_menu);
        setJMenuBar(menubar);
    }
}